package server;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Vector;

import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;

import network.Packet;
import network.Packet_Alive;
import network.Packet_Chunk;
import network.Packet_Message;
import network.Packet_Player;
import network.Packet_Request;
import network.Player;
import network.Receiver;
import network.Packet.packettype;
import network.Packet_Message.MessageType;

import server.Entity.EntityType;
import shared.Vec2f;
import vmo.Chunk;
import vmo.Game;

public class Server implements Runnable{
	
	boolean m_keeprunning;
	long m_lastframe;
	float m_clock;	
	float m_lasttick;
	Thread m_async;
	Receiver m_receiver;
	
	DatagramSocket m_outsocket;
	Vector <Player> m_players;
	
	Simulation m_simulation;
	
	public Server()
	{
		m_simulation=new Simulation();
		m_keeprunning=true;
		m_clock=0;
		m_lastframe=0;
		//setup player list
		m_players=new Vector<Player> ();
		//setup ports for server
		m_receiver=new Receiver(9876);
		m_async=new Thread(m_receiver);
		m_async.start();
		try {
			m_outsocket=new DatagramSocket();
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
	
	void PacketProcessing(Packet packet)
	{
		HandleLastreceived(packet);
		//check if the packet is a handshake from a player we don't have listed
		switch(packet.getType())
		{
			case MESSAGE:
				HandleMessage(packet);
			break;
			case PING:
				HandlePing(packet);
				break;
			case ALIVE:
				HandleAlive(packet);
				break;
			case REQUEST:
				HandleRequest((Packet_Request) packet);
				break;
				
		
		}
		//if so, send an acknowledgement message
		
	}

	void HandleRequest(Packet_Request packet)
	{
		Entity entity=m_simulation.m_entities.get(packet.m_entity);
		if (packet.m_y>=0 && packet.m_y<entity.m_height)
		{
			if (packet.m_x>=0 && packet.m_x<entity.m_width)
			{
				byte b=0b0001;	
				for (int i=0;i<4;i++)
				{
					if ((b & packet.m_bitfield)>0)
					{	
						Packet_Chunk chunk=new Packet_Chunk(packet.m_instance,packet.m_z, (byte)i, (int)m_clock*60, 
							entity.m_chunks[packet.m_x][packet.m_y], packet.getAddress());
						Transmit(chunk,chunk.getAddress(),false);
					}
					b=(byte) (b<<1);	
				}										
			}
			else if (entity.m_type==EntityType.WORLD)
			{
	
				short x=packet.m_x;
				if (x<0)
				{
					x=(short) (entity.m_width+x);
				}
				if (x>=entity.m_width)
				{
					x=(short) (x-entity.m_width);
				}
					Chunk c=entity.m_chunks[x][packet.m_y].Clone();
					c.m_position.x=packet.m_x;
					byte b=0b0001;
				for (int i=0;i<4;i++)
				{
					if ((b & packet.m_bitfield)>0)
					{
						Packet_Chunk chunk=new Packet_Chunk(packet.m_instance,packet.m_z, (byte)i, (int)m_clock*60, 
						c, packet.getAddress());
						Transmit(chunk,chunk.getAddress(),false);
					}
					b=(byte) (b<<1);
				}				
			}
					
		}



	}
	
	void HandleAlive(Packet packet)
	{
		
		
	}
	
	void HandleLastreceived(Packet packet)
	{
		if (m_players.size()>0)
		{
			for (int i=0;i<m_players.size();i++)
			{	
				byte []p=m_players.get(i).getAddress().getAddress();
				byte []c=packet.getAddress().getAddress();
				short d=0;
				for (int j=0;j<c.length;j++)
				{
					if (p[j]==c[j])
					{
						d++;
					}
				}
				if (d==c.length)
				{
					m_players.get(i).m_lastreceived=packet.getTimestamp();
					break;				
				}
			}
		}
		
	}
	
	void HandlePing(Packet packet)
	{
		//send an authoritative time packet to the client
		//this needs to contain a synchronization time pushed 1/2 the gap between 
		//current time and the timestamp
		int t=((int)m_clock*60)-packet.getTimestamp();
		t=t/2;
		Player p=SetLatency(packet.getAddress(),t);	
		t= (int)(t+(m_clock*60));
		p.m_player.m_pos=new Vec2f(24,24);
		p.m_player.m_layer=10;
		p.m_player.m_entity=0;
		p.m_player.m_gender=0;
		p.m_player.m_facing=0;
		Packet_Player pl=new Packet_Player(t,packet.getAddress(),p.m_player);
		pl.m_eheight=m_simulation.m_entities.get(0).m_height;
		pl.m_ewidth=m_simulation.m_entities.get(0).m_width;
		pl.m_Etype=m_simulation.m_entities.get(0).m_type;
		
		
	//	Packet p=new Packet(packettype.PING, t, packet.getAddress());
		Transmit(pl,pl.getAddress(),false);

	}
	
	Player SetLatency(InetAddress address, int Tvalue)
	{
		if (m_players.size()>0)
		{
			for (int i=0;i<m_players.size();i++)
			{	
				byte []p=m_players.get(i).getAddress().getAddress();
				byte []c=address.getAddress();
				short d=0;
				for (int j=0;j<c.length;j++)
				{
					if (p[j]==c[j])
					{
						d++;
					}
				}
				if (d==c.length)
				{
					m_players.get(i).m_latency=Tvalue;
					return m_players.get(i);	
				}
			}
		}	
		return null;
	}
	void HandleLastsent(InetAddress address)
	{
		if (m_players.size()>0)
		{
			for (int i=0;i<m_players.size();i++)
			{	
				if (m_players.get(i).getAddress().getAddress()==address.getAddress())
				{
					m_players.get(i).m_lastreceived=(int) (m_clock*60);
					break;
				}
			}
		}		
		
	}
	void Transmit(Packet p,InetAddress address, boolean handled)
	{
		if (!handled)
		{
			HandleLastsent(address);
		}
		DatagramPacket dp=p.Encode(address,781);
		try {
			m_outsocket.send(dp);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
		
	
	void HandleMessage(Packet packet)
	{
		Packet_Message ph=(Packet_Message)packet;
//	float ticktime=m_clock/60
		switch (ph.m_msgtype)
		{
		case HANDSHAKE:
			float ticktime=m_clock/60;
			Player p=new Player(ph.m_string,(int)ticktime,packet.getAddress());
			m_players.add(p);
			//send an acknowledgement back to the client
			Packet_Message pa=new Packet_Message("server welcomes you", (int)ticktime,MessageType.ACKNOWLEDGEMENT);
			Transmit(pa,packet.getAddress(),false);
						
			break;
		
		}

	}
	
	public void Update(float dt)
	{
		m_clock+=dt/Game.m_tickspersecond;
		//handle all the server processing here...the server is going to get bloated at this rate, but there you go
		Packet p=getPacket();
		//for now, just check if we've not sent a message to a player recently and send keep alive messages
		if (m_clock-0.1F>m_lasttick)
		{
			Tick();
			m_lasttick=m_clock;
		}
		
	}
	
	void Tick()
	{
		if (m_players.size()>0)
		{
			for (int i=m_players.size()-1;i>=0;i--)
			{
				//check last sent
				if (m_players.get(i).m_lastsent<(m_clock*60)-120)
				{
					//send a keep alive message
					Packet_Alive p=new Packet_Alive((int)m_clock*60);
					m_players.get(i).m_lastsent=(int)m_clock*60;
					Transmit(p,m_players.get(i).getAddress(),true);
				}
				
				//check last received, last sent
				if (m_players.get(i).m_lastreceived<(m_clock*60)-300)
				{
					//drop player;
	//				m_players.remove(i);
				
				}
			}
		}
		
	}
	
	public void Discard()
	{
		m_keeprunning=false;
		m_async.interrupt();
		m_receiver.interrupt();	
		m_receiver.Discard();
		m_outsocket.close();
		Savestate();
		
	}
	
	void Savestate() //need to save the state of the server world to a file when the server is shut down
	{
		m_simulation.End();
	}
	
	public Packet getPacket()
	{
		Packet p=null;
		if (m_receiver.PacketWaiting())
		{
			p=m_receiver.TakePacket();			
			//handle player processing
			if (p!=null)
			{
				PacketProcessing(p);
			}
		}
		return p;
		
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		//stand wait update loop goes here
		while (m_keeprunning==true)
		{
			
			long t=Sys.getTime();
			long dt=t-m_lastframe;
			long dadjust=(long) ((long)t/Game.m_tickspersecond);
			Display.sync(60);
			/*
				try {
					Thread.sleep(0);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					m_keeprunning=false;
					e.printStackTrace();
				}
			*/
			m_lastframe=t;
			//timekeeping
			Update(dt);		
		}
	}
	
	
}
